package com.galaxy.device.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.galaxy.device.domain.DeviceEs;
import com.galaxy.device.domain.vo.DeviceLocationVo;
import com.galaxy.device.mapper.DeviceEsMapper;
import com.galaxy.device.mapper.DeviceLocationMapper;
import com.galaxy.device.service.IDeviceEsService;
import com.galaxy.device.utils.EsWrappersUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.easyes.core.biz.EsPageInfo;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsRequest;
import org.elasticsearch.action.admin.cluster.settings.ClusterGetSettingsResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.MainResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.GeoDistanceQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 设备ES服务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceEsServiceImpl implements IDeviceEsService {

    private final DeviceLocationMapper deviceLocationMapper;
    private final RestHighLevelClient restHighLevelClient;

    @Value("${easy-es.index-prefix:galaxy_}")
    private String indexPrefix;

    @Value("${easy-es.address}")
    private String address;

    @Value("${easy-es.schema}")
    private String schema;

    @Value("${easy-es.username:}")
    private String username;

    @Value("${easy-es.password:}")
    private String password;

    private static final String INDEX_NAME = "device";
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final int MAX_RETRY = 3;
    private static final long RETRY_DELAY = 5000; // 5秒

    /**
     * 获取完整的索引名称
     */
    private String getFullIndexName() {
        return indexPrefix + INDEX_NAME;
    }

    /**
     * 检查ES连接
     */
    private boolean checkConnection() {
        try {
            MainResponse response = restHighLevelClient.info(RequestOptions.DEFAULT);
            if (response != null && response.getVersion() != null) {
                log.info("ES连接成功，版本: {}, 地址: {}://{}",
                    response.getVersion().getNumber(), schema, address);
                return true;
            }
            return false;
        } catch (Exception e) {
            log.error("ES连接检查失败: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 检查集群健康状态
     */
    private boolean checkClusterHealth() {
        try {
            ClusterHealthRequest request = new ClusterHealthRequest();
            request.timeout("30s");
            request.masterNodeTimeout("30s");
            ClusterHealthResponse response = restHighLevelClient.cluster().health(request, RequestOptions.DEFAULT);

            String clusterName = response.getClusterName();
            ClusterHealthStatus status = response.getStatus();
            int numberOfNodes = response.getNumberOfNodes();
            int numberOfDataNodes = response.getNumberOfDataNodes();

            log.info("ES集群状态 - 名称: {}, 状态: {}, 节点数: {}, 数据节点数: {}",
                clusterName, status, numberOfNodes, numberOfDataNodes);

            if (status == ClusterHealthStatus.RED) {
                log.error("ES集群状态为RED，表示主分片未分配");
                return false;
            } else if (status == ClusterHealthStatus.YELLOW) {
                log.warn("ES集群状态为YELLOW，表示所有主分片已分配，但副本分片未完全分配");
                // 对于YELLOW状态，我们仍然允许操作继续，但记录警告
                return true;
            }

            return true;
        } catch (Exception e) {
            log.error("ES集群健康检查失败: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 检查安全配置
     */
    private void checkSecuritySettings() {
        try {
            ClusterGetSettingsRequest request = new ClusterGetSettingsRequest();
            request.masterNodeTimeout("30s");
            ClusterGetSettingsResponse response = restHighLevelClient.cluster().getSettings(request, RequestOptions.DEFAULT);

            Settings persistentSettings = response.getPersistentSettings();
            Settings transientSettings = response.getTransientSettings();

            boolean securityEnabled = persistentSettings.getAsBoolean("xpack.security.enabled", false) ||
                                    transientSettings.getAsBoolean("xpack.security.enabled", false);

            if (!securityEnabled) {
                log.warn("警告: Elasticsearch安全特性未启用。建议启用安全特性以保护您的集群。");
                log.warn("请参考官方文档配置安全特性: https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html");
            } else if (username.isEmpty() || password.isEmpty()) {
                log.warn("警告: Elasticsearch安全特性已启用，但未配置认证信息。");
            }
        } catch (Exception e) {
            log.error("检查安全配置失败: {}", e.getMessage());
        }
    }

    /**
     * 检查索引是否存在
     */
    private boolean checkIndexExists() {
        try {
            GetIndexRequest request = new GetIndexRequest(getFullIndexName());
            request.local(false);
            request.humanReadable(true);
            request.includeDefaults(false);
            boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
            if (!exists) {
                log.warn("索引 {} 不存在", getFullIndexName());
            }
            return exists;
        } catch (Exception e) {
            log.error("检查索引存在失败: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 带重试的操作执行
     */
    private <T> T executeWithRetry(EsOperation<T> operation) {
        int retryCount = 0;
        while (retryCount < MAX_RETRY) {
            try {
                if (!checkConnection()) {
                    log.warn("ES连接不可用，等待重试... (尝试 {}/{})", retryCount + 1, MAX_RETRY);
                    TimeUnit.MILLISECONDS.sleep(RETRY_DELAY);
                    retryCount++;
                    continue;
                }

                if (!checkClusterHealth()) {
                    log.warn("ES集群不健康，等待重试... (尝试 {}/{})", retryCount + 1, MAX_RETRY);
                    TimeUnit.MILLISECONDS.sleep(RETRY_DELAY);
                    retryCount++;
                    continue;
                }

                if (!checkIndexExists()) {
                    log.warn("索引不存在，等待重试... (尝试 {}/{})", retryCount + 1, MAX_RETRY);
                    TimeUnit.MILLISECONDS.sleep(RETRY_DELAY);
                    retryCount++;
                    continue;
                }

                // 检查安全配置
                checkSecuritySettings();

                return operation.execute();
            } catch (Exception e) {
                log.error("ES操作失败，重试次数: {}, 错误: {}", retryCount + 1, e.getMessage());
                retryCount++;
                if (retryCount < MAX_RETRY) {
                    try {
                        TimeUnit.MILLISECONDS.sleep(RETRY_DELAY);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        break;
                    }
                }
            }
        }
        throw new RuntimeException("ES操作失败，已达到最大重试次数");
    }

    @Override
    public boolean saveDevice(DeviceEs deviceEs) {
        return executeWithRetry(() -> {
            Map<String, Object> data = new HashMap<>();
            data.put("deviceId", deviceEs.getDeviceId());
            data.put("deviceName", deviceEs.getDeviceName());
            data.put("siteId", deviceEs.getSiteId());
            data.put("siteName", deviceEs.getSiteName());
            data.put("location", deviceEs.getLocation());

            // 格式化日期
            String createTime = formatDateTime(deviceEs.getCreateTime());
            String updateTime = formatDateTime(deviceEs.getUpdateTime());
            data.put("createTime", createTime);
            data.put("updateTime", updateTime);

            // 检查文档是否存在
            Map<String, Object> existingDoc = EsWrappersUtils.getById(getFullIndexName(), deviceEs.getDeviceId());
            if (existingDoc != null) {
                // 如果存在，使用更新
                return EsWrappersUtils.updateById(getFullIndexName(), deviceEs.getDeviceId(), data);
            } else {
                // 如果不存在，使用插入
                IndexRequest indexRequest = new IndexRequest(getFullIndexName())
                    .id(deviceEs.getDeviceId())
                    .source(data, XContentType.JSON);
                restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
                return true;
            }
        });
    }

    @Override
    public boolean saveBatchDevice(List<DeviceEs> deviceEsList) {
        return executeWithRetry(() -> {
            if (CollUtil.isEmpty(deviceEsList)) {
                return false;
            }

            // 使用批量处理
            BulkRequest bulkRequest = new BulkRequest();
            for (DeviceEs deviceEs : deviceEsList) {
                Map<String, Object> data = new HashMap<>();
                data.put("deviceId", deviceEs.getDeviceId());
                data.put("deviceName", deviceEs.getDeviceName());
                data.put("siteId", deviceEs.getSiteId());
                data.put("siteName", deviceEs.getSiteName());
                data.put("location", deviceEs.getLocation());

                // 格式化日期
                String createTime = formatDateTime(deviceEs.getCreateTime());
                String updateTime = formatDateTime(deviceEs.getUpdateTime());
                data.put("createTime", createTime);
                data.put("updateTime", updateTime);

                IndexRequest indexRequest = new IndexRequest(getFullIndexName())
                    .id(deviceEs.getDeviceId())
                    .source(data, XContentType.JSON);
                bulkRequest.add(indexRequest);
            }

            // 设置批量处理参数
            bulkRequest.timeout("2m");
            bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL);

            try {
                BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
                if (bulkResponse.hasFailures()) {
                    log.error("批量保存设备信息到ES失败: {}", bulkResponse.buildFailureMessage());
                    return false;
                }
                return true;
            } catch (Exception e) {
                log.error("批量保存设备信息到ES异常: {}", e.getMessage());
                return false;
            }
        });
    }

    @Override
    public boolean updateDevice(DeviceEs deviceEs) {
        return saveDevice(deviceEs);
    }

    @Override
    public boolean deleteDevice(String deviceId) {
        return executeWithRetry(() ->
            EsWrappersUtils.deleteById(getFullIndexName(), deviceId)
        );
    }

    /**
     * 将Map转换为DeviceEs对象
     */
    private DeviceEs convertMapToDeviceEs(Map<String, Object> map) {
        DeviceEs deviceEs = new DeviceEs();
        deviceEs.setDeviceId((String) map.get("deviceId"));
        deviceEs.setDeviceName((String) map.get("deviceName"));
        deviceEs.setSiteId((String) map.get("siteId"));
        deviceEs.setSiteName((String) map.get("siteName"));

        // 处理 location 字段
        Object locationObj = map.get("location");
        if (locationObj instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> locationMap = (Map<String, Object>) locationObj;
            Double lat = ((Number) locationMap.get("lat")).doubleValue();
            Double lon = ((Number) locationMap.get("lon")).doubleValue();
            deviceEs.setLocation(new GeoPoint(lat, lon));
        } else if (locationObj instanceof GeoPoint) {
            deviceEs.setLocation((GeoPoint) locationObj);
        }

        deviceEs.setCreateTime((String) map.get("createTime"));
        deviceEs.setUpdateTime((String) map.get("updateTime"));
        return deviceEs;
    }

    /**
     * 验证地理位置参数
     */
    private void validateGeoParams(Double longitude, Double latitude, Double radius) {
        if (longitude == null || latitude == null || radius == null) {
            throw new IllegalArgumentException("经度、纬度和半径不能为空");
        }
        if (longitude < -180 || longitude > 180) {
            throw new IllegalArgumentException("经度必须在-180到180度之间");
        }
        if (latitude < -90 || latitude > 90) {
            throw new IllegalArgumentException("纬度必须在-90到90度之间");
        }
        if (radius <= 0) {
            throw new IllegalArgumentException("半径必须大于0");
        }
    }

    /**
     * 构建地理位置查询
     */
    private QueryBuilder buildGeoQuery(Double latitude, Double longitude, Double radius) {
        return EsWrappersUtils.buildGeoDistanceQuery("location", latitude, longitude, radius);
    }

    /**
     * 执行地理位置查询并转换结果
     */
    private List<DeviceEs> executeGeoQuery(QueryBuilder queryBuilder) {
        // 添加查询优化参数
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.trackTotalHits(true);
        searchSourceBuilder.size(10000); // 设置合理的返回大小
        searchSourceBuilder.fetchSource(new String[]{
            "deviceId", "deviceName", "siteId", "siteName", 
            "location", "createTime", "updateTime"
        }, null); // 只获取需要的字段

        // 使用 SearchRequest 构建查询
        SearchRequest searchRequest = new SearchRequest(getFullIndexName());
        searchRequest.source(searchSourceBuilder);

        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            List<Map<String, Object>> result = Arrays.stream(response.getHits().getHits())
                .map(SearchHit::getSourceAsMap)
                .collect(Collectors.toList());
            return result.stream()
                .map(this::convertMapToDeviceEs)
                .collect(Collectors.toList());
        } catch (IOException e) {
            log.error("执行地理位置查询失败", e);
            throw new RuntimeException("执行地理位置查询失败", e);
        }
    }

    /**
     * 执行地理位置分页查询并转换结果
     */
    private EsPageInfo<DeviceEs> executeGeoPageQuery(QueryBuilder queryBuilder, Integer pageNum, Integer pageSize) {
        // 添加查询优化参数
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(queryBuilder);
        searchSourceBuilder.trackTotalHits(true);
        searchSourceBuilder.from((pageNum - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        searchSourceBuilder.fetchSource(new String[]{
            "deviceId", "deviceName", "siteId", "siteName", 
            "location", "createTime", "updateTime"
        }, null); // 只获取需要的字段

        // 使用 SearchRequest 构建查询
        SearchRequest searchRequest = new SearchRequest(getFullIndexName());
        searchRequest.source(searchSourceBuilder);

        try {
            SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            List<Map<String, Object>> result = Arrays.stream(response.getHits().getHits())
                .map(SearchHit::getSourceAsMap)
                .collect(Collectors.toList());
            List<DeviceEs> deviceList = result.stream()
                .map(this::convertMapToDeviceEs)
                .collect(Collectors.toList());

            long total = response.getHits().getTotalHits().value;
            EsPageInfo<DeviceEs> pageInfo = new EsPageInfo<>(deviceList);
            pageInfo.setPageNum(pageNum);
            pageInfo.setPageSize(pageSize);
            pageInfo.setPages((int) Math.ceil((double) total / pageSize));
            pageInfo.setTotal(total);
            return pageInfo;
        } catch (IOException e) {
            log.error("执行地理位置分页查询失败", e);
            throw new RuntimeException("执行地理位置分页查询失败", e);
        }
    }

    @Override
    public List<DeviceEs> listNearbyDevices(Double longitude, Double latitude, Double distance) throws IOException {
        validateGeoParams(longitude, latitude, distance);
        final Double finalLongitude = longitude;
        final Double finalLatitude = latitude;
        final Double finalDistance = distance;
        return executeWithRetry(() -> {
            try {
                QueryBuilder queryBuilder = buildGeoQuery(finalLatitude, finalLongitude, finalDistance);
                List<DeviceEs> deviceList = executeGeoQuery(queryBuilder);
                log.info("成功查询到 {} 个附近设备 (中心点: [{}, {}], 距离: {}km)",
                    deviceList.size(), finalLongitude, finalLatitude, finalDistance);
                return deviceList;
            } catch (Exception e) {
                log.error("查询附近设备失败: 中心点 [{}, {}], 距离 {}km", finalLongitude, finalLatitude, finalDistance, e);
                throw e;
            }
        });
    }

    @Override
    public EsPageInfo<DeviceEs> pageNearbyDevices(Double longitude, Double latitude, Double distance, Integer pageNum, Integer pageSize) throws IOException {
        validateGeoParams(longitude, latitude, distance);
        if (pageNum == null || pageNum < 1) {
            pageNum = 1;
        }
        if (pageSize == null || pageSize < 1) {
            pageSize = 10;
        }

        final Double finalLongitude = longitude;
        final Double finalLatitude = latitude;
        final Double finalDistance = distance;
        final Integer finalPageNum = pageNum;
        final Integer finalPageSize = pageSize;

        return executeWithRetry(() -> {
            try {
                QueryBuilder queryBuilder = buildGeoQuery(finalLatitude, finalLongitude, finalDistance);
                EsPageInfo<DeviceEs> pageInfo = executeGeoPageQuery(queryBuilder, finalPageNum, finalPageSize);
                log.info("成功分页查询到 {} 个附近设备 (中心点: [{}, {}], 距离: {}km, 页码: {}, 每页: {})",
                    pageInfo.getTotal(), finalLongitude, finalLatitude, finalDistance, finalPageNum, finalPageSize);
                return pageInfo;
            } catch (Exception e) {
                log.error("分页查询附近设备失败: 中心点 [{}, {}], 距离 {}km, 页码 {}, 每页 {}",
                    finalLongitude, finalLatitude, finalDistance, finalPageNum, finalPageSize, e);
                throw e;
            }
        });
    }

    @Override
    public List<DeviceEs> listDevicesInCircle(Double longitude, Double latitude, Double radius) throws IOException {
        validateGeoParams(longitude, latitude, radius);
        final Double finalLongitude = longitude;
        final Double finalLatitude = latitude;
        final Double finalRadius = radius;
        return executeWithRetry(() -> {
            try {
                QueryBuilder queryBuilder = buildGeoQuery(finalLatitude, finalLongitude, finalRadius);
                List<DeviceEs> deviceList = executeGeoQuery(queryBuilder);
                log.info("成功查询到 {} 个设备在圆形范围内 (中心点: [{}, {}], 半径: {}km)",
                    deviceList.size(), finalLongitude, finalLatitude, finalRadius);
                return deviceList;
            } catch (Exception e) {
                log.error("查询圆形范围内设备失败: 中心点 [{}, {}], 半径 {}km", finalLongitude, finalLatitude, finalRadius, e);
                throw e;
            }
        });
    }

    @Override
    public EsPageInfo<DeviceEs> pageDevicesInCircle(Double longitude, Double latitude, Double radius, Integer pageNum, Integer pageSize) throws IOException {
        validateGeoParams(longitude, latitude, radius);
        if (pageNum == null || pageNum < 1) {
            pageNum = 1;
        }
        if (pageSize == null || pageSize < 1) {
            pageSize = 10;
        }

        final Double finalLongitude = longitude;
        final Double finalLatitude = latitude;
        final Double finalRadius = radius;
        final Integer finalPageNum = pageNum;
        final Integer finalPageSize = pageSize;

        return executeWithRetry(() -> {
            try {
                QueryBuilder queryBuilder = buildGeoQuery(finalLatitude, finalLongitude, finalRadius);
                EsPageInfo<DeviceEs> pageInfo = executeGeoPageQuery(queryBuilder, finalPageNum, finalPageSize);
                log.info("成功分页查询到 {} 个设备在圆形范围内 (中心点: [{}, {}], 半径: {}km, 页码: {}, 每页: {})",
                    pageInfo.getTotal(), finalLongitude, finalLatitude, finalRadius, finalPageNum, finalPageSize);
                return pageInfo;
            } catch (Exception e) {
                log.error("分页查询圆形范围内设备失败: 中心点 [{}, {}], 半径 {}km, 页码 {}, 每页 {}",
                    finalLongitude, finalLatitude, finalRadius, finalPageNum, finalPageSize, e);
                throw e;
            }
        });
    }

    @Override
    public boolean saveOrUpdateDeviceLocation(DeviceLocationVo deviceLocationVo) {
        return executeWithRetry(() -> {
            if (!deviceLocationVo.isValid()) {
                log.warn("设备位置信息无效: {}", deviceLocationVo);
                return false;
            }

            // 转换为ES对象
            DeviceEs deviceEs = deviceLocationVo.toDeviceEs();

            // 检查是否存在
            Map<String, Object> existingDevice = EsWrappersUtils.getById(getFullIndexName(), deviceEs.getDeviceId());

            if (existingDevice != null) {
                // 更新
                return updateDevice(deviceEs);
            } else {
                // 新增
                return saveDevice(deviceEs);
            }
        });
    }

    /**
     * 格式化日期时间
     */
    private String formatDateTime(String dateTimeStr) {
        if (dateTimeStr == null) {
            return null;
        }
        try {
            // 尝试解析ISO格式的日期时间
            LocalDateTime dateTime = LocalDateTime.parse(dateTimeStr);
            return dateTime.format(DATE_FORMATTER);
        } catch (Exception e) {
            try {
                // 尝试使用hutool解析
                return DateUtil.format(DateUtil.parse(dateTimeStr), "yyyy-MM-dd HH:mm:ss");
            } catch (Exception ex) {
                log.warn("日期格式转换失败: {}", dateTimeStr);
                return null;
            }
        }
    }

    /**
     * ES操作接口
     */
    @FunctionalInterface
    private interface EsOperation<T> {
        T execute() throws Exception;
    }
}
